# Page layout. Defined in terms of nested horizontal and vertical stacks, with scalable images and 
# rubbers as leaves, and with frame and grid decorators.
# Note that a layout pulls its parameters from its attributes, not from its parent's tributes, as
# the former are not inheritable. E.g. a width attribute set for a frame layout is meant only to be used
# by that frame, not by its child layout.

# Eager fill algorithm:
# + Determine the minimimum dimensions of each element. Fail if there's not enough space.
# + Traverse all elements in order of priority (high to low) and offer each their minimal dimensions plus
# all of the remaining space.
# + Traverse again to set the positions.
# Priority: Is an attribute with default value -1 for elements that can't expand, 1 for images, the maximum
# positive priority of any of their children for containers (decorators and stacks) and 0 for everyone else.
# Equal fill algorithm:
# + Determine the minimimum dimensions of each element. Fail if there's not enough space.
# + Traverse all elements in order of reverse priority (low to high) and offer each their minimal dimensions
# plus a part of the remaining space relative to the weight of the element compared to the weight of all
# elements.
# + Traverse again to set the positions.
# Weight: Is an attribute with default value 0 for elements that can't expand, 1 for non-stacks and the number
# of elements in the stack for stacks.

# Limit width and height. Available to all layouts.
# - Level 0: don't (default)
# - Level 1: after gathering, i.e. equal to the dimension set in the attributes
# - Level 2: after examining, i.e. equal to the dimension set by children, or other calculations
# - ?Level -1: after resetting, i.e. equal to the default (only makes sense in subclasses that
#   have set a meaningful default, I guess, although I can't see a reason to forbid alterations
#   by the attributes, since these attributes are not inherited.)
# Fit width and height. Only available to certain layouts.
# - Level 0: don't (default)
# - Level 1: when filling, i.e. equal to the dimension requested by the parent when filling
# - Level 2: when fitting, i.e. equal to the dimension requested by the parent when fitting,
#   that is when the final dimensions are known.
# Effect on the different layouts:
# - layout: Examine hardly does a thing, therefore limit level 1 same as level 2.
#   No fit, because expands to match the size anyway.
# - image layout: Adapts scale to the smallest of the two dimensions. Does not promise perfect fit.
#   Limit level 1 poses a further restriction on this scale adaption.
#   No fit, because no special effect. 
# - stack layout:
#   Limit level 2, listens to the needs of the children, keeps gap fixed. 
#   Fit: expands gap in its primary dimension to make up for what space children left;
#   centers children according to the pull ratio in its secondary dimension.
# - frame layout:
#   Limit level 2, listens to what the child requires, keeps margin constant.
#   Fit: expands margins according to the pull ratios to obtain an exact fit.
# - grid layout:
#   Limit level 2, listens to what the child requires.
#   Fit: adds extra cells (increases the length), but does not guarantee exact fit;
#   centers child in this area according to the pull ratios. 
# Auto fill

# Layouts. Primitive layout. No children.

# Layout. Base class.
itcl::class Layout {
	inherit Play

	constructor {attributes} {Play::constructor $attributes} {}

	# Note that we overrule the resolve function, not the doResolve function.
	# The reason is that we want to expose the variables generated by the parent
	# layout to this layout. As such, you can for example define the width
	# attribute as {$Width 2 div}, with $Width taken from the parent layout.
	# Useful for positioning floating layouts.
	method resolve {tributes} {
		if $iActivated {autoFill $tributes}
		
		chain $tributes
	}
	
	method prune {tributes} {
		# In case there are no references, the resolve step can be skipped and
		# you can call this function directly.
		if $iActivated {autoFill $tributes}

		chain $tributes		
	}

	protected method doPrune {variablesPtr} {
		upvar $variablesPtr variables

		if [$iAttributes has width] {
			detect [$iAttributes of width] variables
		}
		if [$iAttributes has height] {
			detect [$iAttributes of height] variables
		}
		if [$iAttributes has left] {
			detect [$iAttributes of left] variables
		}
		if [$iAttributes has bottom] {
			detect [$iAttributes of bottom] variables
		}
		if [$iAttributes has limitWidth] {
			detect [$iAttributes of limitWidth] variables
		}
		if [$iAttributes has limitHeight] {
			detect [$iAttributes of limitHeight] variables
		}
		if [$iAttributes has allowZeroWidth] {
			detect [$iAttributes of allowZeroWidth] variables
		}
		if [$iAttributes has allowZeroHeight] {
			detect [$iAttributes of allowZeroHeight] variables
		}
		if [$iAttributes has widthPriority] {
			detect [$iAttributes of widthPriority] variables
		}
		if [$iAttributes has heightPriority] {
			detect [$iAttributes of heightPriority] variables
		}
		if [$iAttributes has horizontalPullRatio] {
			detect [$iAttributes of horizontalPullRatio] variables
		}
		if [$iAttributes has verticalPullRatio] {
			detect [$iAttributes of verticalPullRatio] variables
		}
		if [$iAttributes has horizontalWeight] {
			detect [$iAttributes of horizontalWeight] variables
		}
		if [$iAttributes has verticalWeight] {
			detect [$iAttributes of verticalWeight] variables
		}
		if [$iAttributes has positionBox] {
			detect [$iAttributes of positionBox] variables
		}

		chain variables
	}

	method autoWrite {U tributes} {
		# In case there are no references and no pruning, those steps can be skipped and
		# you can call this function directly.
		if $iActivated {autoFill $tributes}

		chain $U $tributes
	}

	# An autoFill calculates a layout based on the width and height set in its attributes, and centers the result.
	# You can call this function on your top layout element to force a fill calculation. The top element should have
	# both width and height set in its attributes. Alternatively you can call the activate method (see below) on the
	# same top layout element. Especially useful if you can't be bothered to collect all tributes.
	method autoFill {tributes} {
		prepare $tributes
		fill $iInitialWidth $iInitialHeight
		if [llength $iPositionBox] {
			eval position $iPositionBox
		} else {
			position $iLeft $iBottom $iWidth $iHeight
		}
	}

	# Cheap implementation of a dirty bit. After making an adjustment somewhere in the layout, the programmer
	# can call this function on the top layout element and at the appropriate time (before pruning or writing),
	# the autoFill function will be called (actually the doAutoFill function).
	method activate {} {
		set iActivated 1
	}

	# You can set up a trigger path to a descendant of this layout. Whenever this layout is resized or moved,
	# the descendant will be notified by calling its activate function, so that it in turn can reposition itself.
	# Only useful for descendants that themselves don't alter the calculations for this layout, but whose position
	# and/or size depend on this layout. E.g. a title box which you want to locate in a corner of an image. 
	method addTriggerPath {triggerPath} {
		if {![$iAttributes has triggers]} {
			$iAttributes set triggers [Story::toTextList $triggerPath]
		} else {
			$iAttributes set triggers "[$iAttributes of triggers] [Story::toTextList $triggerPath]"
		}
	}

	method removeTriggerPath {triggerPath} {
		assert [$iAttributes has triggers] {
			Unable to remove <$triggerPath> from an empty list of triggers.
		}
		set triggerTextPaths [$iAttributes of triggers]
		assert [string length $triggerTextPaths] {
			Unable to remove <$triggerPath> from an empty list of triggers.
		}
		set currentTriggerPath [Story::fromTextList $triggerTextPaths triggerTextPaths]
		# List equality would be better, though slower.
		if {[string equal $currentTriggerPath $triggerPath]} {
			set triggerTextPaths [string trim $triggerTextPaths]
			if [string length $triggerTextPaths] {
				$iAttributes set triggers $triggerTextPaths
			} else {
				$iAttributes unset triggers
			}
			return
		}
		
		set frontTriggerTextPaths [Story::toTextList $currentTriggerPath]
		while {[string length $triggerTextPaths]} {
			set currentTriggerPath [Story::fromTextList $triggerTextPaths triggerTextPaths]
			# List equality would be better, though slower.
			if {[string equal $currentTriggerPath $triggerPath]} {
				$iAttributes set triggers "$frontTriggerTextPaths$triggerTextPaths"
				return
			}
			append frontTriggerTextPaths " [Story::toTextList $currentTriggerPath]"
		}
		
		# Not found. Report.
		assert 0 {
			Unable to find and remove <$triggerPath> from the list of triggers.
		}
	}

	method getTriggerPaths {} {
		if {![$iAttributes has triggers]} {return {}}
		set triggerPaths {}
		set triggerTextPaths [$iAttributes of triggers]
		while {[string length $triggerTextPaths]} {
			lappend triggerPaths [Story::fromTextList $triggerTextPaths triggerTextPaths]
		}
		return $triggerPaths
	}

	protected method trigger {} {
		foreach triggerPath [getTriggerPaths] {
			set layout [findLayout $triggerPath]
			$layout activate
			$layout updateAttributes [list positionBox "$iLeft $iBottom $iWidth $iHeight"]
		}
	}

	method prepare {tributes} {
		#puts "$this -> [$iSequence get]" 
		removeAttributes {$Width $Height $Left $Bottom $InitialWidth $InitialHeight}

		assignTributes $tributes
		doPrepare
		releaseTributes
	}

	protected method doPrepare {} {
		# We split this up in three separate protected methods for maximum flexibility.

		# Set default values.
		reset
		# Extract attributes and check validity.
		gather
		# Compare the parameter values, tuning them to each other.
		examine
	}

	# Set default values.
	protected method reset {} {
		set iWidth 0
		set iHeight 0
		set iInitialWidth 0
		set iInitialHeight 0
		set iLeft 0
		set iBottom 0
		set iWidthPriority 0
		set iHeightPriority 0
		set iLimitWidth $iActivated
		set iLimitHeight $iActivated
		set iAllowZeroWidth 0
		set iAllowZeroHeight 0
		set iFixWidth 0
		set iFixHeight 0
		set iHorizontalPullRatio .5
		set iVerticalPullRatio .5
		set iHorizontalWeight 1
		set iVerticalWeight 1
		set iPositionBox {}
	}

	# Extract attributes and check validity. This is normally the only call in the doPrepare implementation
	# that needs access to iTributes, normally through its various calls to the evaluate method.
	protected method gather {} {
		# Initial dimensions
		# We need to put these initial dimensions in separate variables, since it may occur that the fill
		# method is called more than once on the same element without intermediate calls to prepare.
		if [$iAttributes has width] {
			set iInitialWidth [evaluate [$iAttributes of width]]
			assert {[Story::isGreaterOrEqual $iInitialWidth 0]} {
				Illegal width: [Story::dedouble $iInitialWidth] is not a positive number.
			}
		}
		if [$iAttributes has height] {
			set iInitialHeight [evaluate [$iAttributes of height]]
			assert {[Story::isGreaterOrEqual $iInitialHeight 0]} {
				Illegal height: [Story::dedouble $iInitialHeight] is not a positive number.
			}
		}
		# Initial position
		if [$iAttributes has left] {
			set iLeft [evaluate [$iAttributes of left]]
		}
		if [$iAttributes has bottom] {
			set iBottom [evaluate [$iAttributes of bottom]]
		}
		# Priority. Will determine the precedence for the stack layout fill algorithms.
		if [$iAttributes has widthPriority] {
			set iWidthPriority [evaluate [$iAttributes of widthPriority]]
		}
		if [$iAttributes has heightPriority] {
			set iHeightPriority [evaluate [$iAttributes of heightPriority]]
		}
		# Is this box allowed to expand or not? If not, set the priority lower than normal, so that
		# the other boxes can fully profit from any expansion. (see the examine method)
		if [$iAttributes has limitWidth] {
			set iLimitWidth [evaluate [$iAttributes of limitWidth]]
		}
		if [$iAttributes has limitHeight] {
			set iLimitHeight [evaluate [$iAttributes of limitHeight]]
		}
		# Zero width/height result will normally trigger an error. These booleans disable those checks.
		if [$iAttributes has allowZeroWidth] {
			set iAllowZeroWidth [Story::ofBoolean [evaluate [$iAttributes of allowZeroWidth]]]
		}
		if [$iAttributes has allowZeroHeight] {
			set iAllowZeroHeight [Story::ofBoolean [evaluate [$iAttributes of allowZeroHeight]]]
		}
		# In case of excess space, e.g. if autofilling or fitting an element to a certain width or height,
		# the element will be positioned in this space by pulling them to one or the other side according
		# to these ratios.
		if [$iAttributes has horizontalPullRatio] {
			set iHorizontalPullRatio [evaluate [$iAttributes of horizontalPullRatio]]
			assert {[Story::isGreaterOrEqual $iHorizontalPullRatio 0] && [Story::isSmallerOrEqual $iHorizontalPullRatio 1]} {
				The horizontal pull ratio needs to be between 0 and 1, but equals [Story::dedouble $iHorizontalPullRatio 5].
			}
		}
		if [$iAttributes has verticalPullRatio] {
			set iVerticalPullRatio [evaluate [$iAttributes of verticalPullRatio]]
			assert {[Story::isGreaterOrEqual $iVerticalPullRatio 0] && [Story::isSmallerOrEqual $iVerticalPullRatio 1]} {
				The vertical pull ratio needs to be between 0 and 1, but equals [Story::dedouble $iVerticalPullRatio 5].
			}
		}

		# Weight. Will determine how big a part this layout will receive in the equal fill algorithm.
		if [$iAttributes has horizontalWeight] {
			set iHorizontalWeight [evaluate [$iAttributes of horizontalWeight]]
			assert {[Story::isGreaterOrEqual $iHorizontalWeight 0]} {
				Illegal horizontal weight: [Story::dedouble $iHorizontalWeight] is not a positive number.
			}
		}
		if [$iAttributes has verticalWeight] {
			set iVerticalWeight [evaluate [$iAttributes of verticalWeight]]
			assert {[Story::isGreaterOrEqual $iVerticalWeight 0]} {
				Illegal vertical weight: [Story::dedouble $iVerticalWeight 5] is not a positive number.
			}
		}
		
		# Position box. Consists of 4 numbers, left bottom width height.
		# Used by the autoFill method to position the element.
		if [$iAttributes has positionBox] {
			set iPositionBox [evaluate [$iAttributes of positionBox]]
			if {[llength $iPositionBox] > 0} {
				assert {[llength $iPositionBox] == 4} {
					Illegal position box: <$iPositionBox> does not contain 4 numbers.
				}
				foreach element $iPositionBox {
					assert [string is double $element] {
						Illegal position box element: <$element> in <$iPositionBox> is not a number.
					}
				}
			}
		}
	}

	# Compare the parameter values, tuning them to each other. As this is the last method called in doPrepare, 
	# it's preferable that the chain call in a subclass is put at the end of the method body.
	protected method examine {} {
		if $iLimitWidth {set iWidthPriority -1; set iHorizontalWeight 0}
		if $iLimitHeight {set iHeightPriority -1; set iVerticalWeight 0}

		# Just in case no fill is done.
		set iWidth $iInitialWidth
		set iHeight $iInitialHeight
	}

	method getWidth {} {return $iWidth}
	method getHeight {} {return $iHeight}

	method getInitialWidth {} {
		#puts "iw $this -> $iInitialWidth";
		return $iInitialWidth
	}
	method getInitialHeight {} {
		#puts "ih $this -> $iInitialHeight";
		return $iInitialHeight
	}

	method getLeft {} {return $iLeft}
	method getBottom {} {return $iBottom}
	method getRight {} {return [expr $iLeft + $iWidth]}
	method getTop {} {return [expr $iBottom + $iHeight]}

	method getWidthPriority {} {return $iWidthPriority}
	method getHeightPriority {} {return $iHeightPriority}

	method getHorizontalWeight {} {return $iHorizontalWeight}
	method getVerticalWeight {} {return $iVerticalWeight}

	method fill {width height} {
		#puts "[info class] $width $height $iInitialWidth $iInitialHeight $iWidth $iHeight"
		assert {[Story::isSmallerOrEqual $iInitialWidth $width]} {
			Width too small, need at least another [Story::dedouble [expr $iInitialWidth - $width]] points.
		}
		assert {[Story::isSmallerOrEqual $iInitialHeight $height]} {
			Height too small, need at least another [Story::dedouble [expr $iInitialHeight - $height]] points.
		}
		if $iLimitWidth {set iWidth $iInitialWidth} else {set iWidth $width}
		if $iLimitHeight {set iHeight $iInitialHeight} else {set iHeight $height}
		assert {$iAllowZeroWidth || [Story::isGreater $iWidth 0]} {
			Fillable width [Story::dedouble $iWidth] should be greater than zero.
		} 
		assert {$iAllowZeroHeight || [Story::isGreater $iHeight 0]} {
			Fillable height [Story::dedouble $iHeight] should be greater than zero.
		}

		doFill

		assert {$iAllowZeroWidth || [Story::isGreater $iWidth 0]} {
			Filled width [Story::dedouble $iWidth] should be greater than zero.
		} 
		assert {$iAllowZeroHeight || [Story::isGreater $iHeight 0]} {
			Filled height [Story::dedouble $iHeight] should be greater than zero.
		} 
		assert {[Story::isSmallerOrEqual $iWidth $width]} {
			Algorithm problem: Expanded [expr $iWidth - $width] points beyond the allowed width.
		}
		assert {[Story::isSmallerOrEqual $iHeight $height]} {
			Algorithm problem: Expanded [expr $iHeight - $height] points beyond the allowed height.
		}

		updateAttributes [list \$Width $iWidth \$Height $iHeight \
			\$InitialWidth $iInitialWidth \$InitialHeight $iInitialHeight \
			\$Left $iLeft \$Bottom $iBottom]
	}

	protected method doFill {} {}

	method position {left bottom width height} {
		set iLeft [expr $iLeft + $left + ($width - $iWidth) * $iHorizontalPullRatio]
		set iBottom [expr $iBottom + $bottom + ($height - $iHeight) * $iVerticalPullRatio]
		#puts "$this: $left $bottom $width $height $iLeft $iBottom $iWidth $iHeight"

		updateAttributes [list \$Left $iLeft \$Bottom $iBottom]
		
		trigger

		set iActivated 0
	}

	method translate {hor vert} {
		set iLeft [expr $iLeft + $hor]
		set iBottom [expr $iBottom + $vert]
		updateAttributes [list \$Left $iLeft \$Bottom $iBottom]
		
		trigger
	}

	# Utility function
	method findLayout {layoutPath} {
		set layout [findPart $layoutPath]
		assert [$layout isa Layout] {
			The <[lindex $layoutPath end]> element is not a proper Layout.
		}
		return $layout
	}

	variable iWidth
	variable iHeight
	variable iInitialWidth
	variable iInitialHeight
	variable iLeft
	variable iBottom
	variable iWidthPriority
	variable iHeightPriority
	variable iLimitWidth
	variable iLimitHeight
	variable iAllowZeroWidth
	variable iAllowZeroHeight
	variable iFixWidth
	variable iFixHeight
	variable iHorizontalPullRatio
	variable iVerticalPullRatio
	variable iHorizontalWeight
	variable iVerticalWeight
	variable iPositionBox
	variable iActivated 0
}

# A scalable image needs to know ImageWidth and ImageHeight.
# It will scale itself to fill the box in its most restrictive dimension.
# Images have by default a priority of 1 in any dimension, so that they will be the primary choice
# for the various fill algorithms (if all priorities remain unchanged).
itcl::class ImageLayout {
	inherit Layout

	constructor {attributes} {Layout::constructor $attributes} {}

	protected method doPrune {variablesPtr} {
		upvar $variablesPtr variables

		if [$iAttributes has imageWidth] {
			detect [$iAttributes of imageWidth] variables
		}
		if [$iAttributes has imageHeight] {
			detect [$iAttributes of imageHeight] variables
		}

		chain variables
	}

	method prepare {tributes} {
		removeAttributes {$ImageScale $ImageWidth $ImageHeight}

		chain $tributes
	}

	protected method reset {} {
		chain

		set iImageWidth 0
		set iImageHeight 0
		set iWidthPriority 1
		set iHeightPriority 1
	}

	protected method gather {} {
		chain

		if [$iAttributes has imageWidth] {
			set iImageWidth [evaluate [$iAttributes of imageWidth]]
			assert {[Story::isGreaterOrEqual $iImageWidth 0]} {
				Illegal imageWidth: [Story::dedouble $iImageWidth] is not a positive number.
			}
		}
		if [$iAttributes has imageHeight] {
			set iImageHeight [evaluate [$iAttributes of imageHeight]]
			assert {[Story::isGreaterOrEqual $iImageHeight 0]} {
				Illegal imageHeight: [Story::dedouble $iImageHeight] is not a positive number.
			}
		}
	}

	protected method examine {} {
		assert {[Story::isGreater $iImageWidth 0] && [Story::isGreater $iImageHeight 0]} {
			[info class] requires imageWidth [Story::dedouble $iImageWidth] and imageHeight [Story::dedouble $iImageHeight] to be greater than 0.
		}

		if {($iInitialWidth > 0) || ($iInitialHeight > 0)} {
			if {$iInitialWidth * $iImageHeight > $iInitialHeight * $iImageWidth} {
				if {$iLimitHeight != 1} {
					set iInitialHeight [expr $iInitialWidth * $iImageHeight / double($iImageWidth)]
				} else {
					set iInitialWidth [expr $iInitialHeight * $iImageWidth / double($iImageHeight)]
				}
			} else {
				if {$iLimitWidth != 1} {
					set iInitialWidth [expr $iInitialHeight * $iImageWidth / double($iImageHeight)]
				} else {
					set iInitialHeight [expr $iInitialWidth * $iImageHeight / double($iImageWidth)]
				}
			}
		}

		chain
	}

	method fill {width height} {
		chain $width $height

		updateAttributes [list \$ImageScale [expr $iWidth / double($iImageWidth)] \
			\$ImageWidth $iImageWidth \$ImageHeight $iImageHeight]
	}

	protected method doFill {} {
		if {$iWidth * $iImageHeight < $iHeight * $iImageWidth} {
			set iHeight [expr $iWidth * $iImageHeight / double($iImageWidth)]
		} else {
			set iWidth [expr $iHeight * $iImageWidth / double($iImageHeight)]
		}
	}

	variable iImageWidth
	variable iImageHeight
}


# Sequences. One or more children which will be arranged according to the applied algorithm.
# Children, which have to be part of the Sequence, are indicated (and ordered) by calling addLayout 
# with the name under which the element is stored.

# SequenceLayout. Base class.
itcl::class SequenceLayout {
	inherit Layout

	constructor {attributes} {Layout::constructor $attributes} {}

	# Instead of simply creating a list of paths, one could also create a tree: Whenever two 
	# neighbouring paths have the same root, they can be joined together, which could possibly
	# compress the layouts representation. E.g. {{root leaf1} {root leaf2}} could be compressed to
	# {root {leaf1 leaf2}} Requires extra programming though, and extra computations, not worth
	# the gain. 

	method addLayoutPath {layoutPath} {
		if {![$iAttributes has layouts]} {
			$iAttributes set layouts [Story::toTextList $layoutPath]
		} else {
			$iAttributes set layouts "[$iAttributes of layouts] [Story::toTextList $layoutPath]"
		}
	}

	method removeLayoutPath {layoutPath} {
		assert [$iAttributes has layouts] {
			Unable to remove <$layoutPath> from an empty list of layouts.
		}
		set layoutTextPaths [$iAttributes of layouts]
		assert [string length $layoutTextPaths] {
			Unable to remove <$layoutPath> from an empty list of layouts.
		}
		set currentLayoutPath [Story::fromTextList $layoutTextPaths layoutTextPaths]
		# List equality would be better, though slower.
		if {[string equal $currentLayoutPath $layoutPath]} {
			set layoutTextPaths [string trim $layoutTextPaths]
			if [string length $layoutTextPaths] {
				$iAttributes set layouts $layoutTextPaths
			} else {
				$iAttributes unset layouts
			}
			return
		}
		
		set frontLayoutTextPaths [Story::toTextList $currentLayoutPath]
		while {[string length $layoutTextPaths]} {
			set currentLayoutPath [Story::fromTextList $layoutTextPaths layoutTextPaths]
			# List equality would be better, though slower.
			if {[string equal $currentLayoutPath $layoutPath]} {
				$iAttributes set layouts "$frontLayoutTextPaths$layoutTextPaths"
				return
			}
			append frontLayoutTextPaths " [Story::toTextList $currentLayoutPath]"
		}
		
		# Not found. Report.
		assert 0 {
			Unable to find and remove <$layoutPath> from the list of layouts.
		}
	}

	method getLayoutPaths {} {
		if {![$iAttributes has layouts]} {return {}}
		set layoutPaths {}
		set layoutTextPaths [$iAttributes of layouts]
		while {[string length $layoutTextPaths]} {
			lappend layoutPaths [Story::fromTextList $layoutTextPaths layoutTextPaths]
		}
		return $layoutPaths
	}

	protected method reset {} {
		chain

		# Prepare the child layouts.
		set iLayouts {}
		foreach layoutPath [getLayoutPaths] {
			set layout [findLayout $layoutPath]

			assert {[lsearch $iLayouts $layout] == -1} {
				The layout <$layoutPath> is included more than once in the layouts of this [info class].
			}
			lappend iLayouts $layout			

			$layout prepare [$iTributes get]

			# Increase the default priority to the maximum of the child layout priorities.
			if {[$layout getWidthPriority] > $iWidthPriority} {set iWidthPriority [$layout getWidthPriority]}
			if {[$layout getHeightPriority] > $iHeightPriority} {set iHeightPriority [$layout getHeightPriority]}
		}
	}

	# The subclasses will take care of adapting and positioning the child layouts in their doFill method.

	method translate {hor vert} {
		chain $hor $vert

		foreach layout $iLayouts {
			$layout translate $hor $vert
		}
	}

	variable iLayouts {}
}

# Stack layouts. The core of the layout process. They come in two flavors: horizontal and vertical.

# HorizontalStackLayout. 
itcl::class HorizontalStackLayout {
	inherit SequenceLayout

	constructor {attributes} {SequenceLayout::constructor $attributes} {}

	protected method doPrune {variablesPtr} {
		upvar $variablesPtr variables

		if [$iAttributes has fillAlgorithm] {
			detect [$iAttributes of fillAlgorithm] variables
		}
		if [$iAttributes has leftToRight] {
			detect [$iAttributes of leftToRight] variables
		}
		if [$iAttributes has gapWidth] {
			detect [$iAttributes of gapWidth] variables
		}
		if [$iAttributes has fitWidth] {
			detect [$iAttributes of fitWidth] variables
		}
		if [$iAttributes has fitHeight] {
			detect [$iAttributes of fitHeight] variables
		}

		chain variables
	}

	method prepare {tributes} {
		removeAttributes {$GapWidth $InitialGapWidth}

		chain $tributes
	}

	protected method reset {} {
		chain

		set iFillAlgorithm eagerFill
		set iLeftToRight 1
		set iGapWidth 0
		set iInitialGapWidth 0
		set iFitWidth 0
		set iFitHeight 0
		
		# Set the default horizontal weight equal to the number of layouts.
		set iHorizontalWeight [llength $iLayouts]
	}

	protected method gather {} {
		chain

		if [$iAttributes has fillAlgorithm] {
			set iFillAlgorithm [evaluate [$iAttributes of fillAlgorithm]]
			assert {[lsearch {eagerFill equalFill} $iFillAlgorithm] != -1} {
				<$iFillAlgorithm> is not a recognized fill algorithm.
			}
		}
		if [$iAttributes has leftToRight] {
			set iLeftToRight [Story::ofBoolean [evaluate [$iAttributes of leftToRight]]]
		}
		if [$iAttributes has gapWidth] {
			set iInitialGapWidth [evaluate [$iAttributes of gapWidth]]
			assert {[Story::isGreaterOrEqual $iInitialGapWidth 0]} {
				Illegal gapWidth: [Story::dedouble $iInitialGapWidth] is not a positive number.
			}
		}
		if [$iAttributes has fitWidth] {
			set iFitWidth [evaluate [$iAttributes of fitWidth]]
		}
		if [$iAttributes has fitHeight] {
			set iFitHeight [evaluate [$iAttributes of fitHeight]]
		}
	}

	protected method examine {} {
		assert {[llength $iLayouts] >= 1} {
			A [info class] element requires at least one valid child layout.
		}

		# Determine minimum dimensions.
		set minimumWidth [expr ([llength $iLayouts] - 1) * $iInitialGapWidth]
		set minimumHeight $iInitialHeight
		foreach layout $iLayouts {
			set minimumWidth [expr $minimumWidth + [$layout getInitialWidth]]
			if {[$layout getInitialHeight] > $minimumHeight} {
				set minimumHeight [$layout getInitialHeight]
			}
		}
		if {[Story::isGreater $minimumWidth $iInitialWidth]} {
			assert {$iLimitWidth != 1} {
				Allowed width too small, need at least another [Story::dedouble [expr $minimumWidth - $iInitialWidth]] points.
			}
			set iInitialWidth $minimumWidth
		}
		if {[Story::isGreater $minimumHeight $iInitialHeight]} {
			assert {$iLimitHeight != 1} {
				Allowed height too small, need at least another [Story::dedouble [expr $minimumHeight - $iInitialHeight]] points.
			}
			set iInitialHeight $minimumHeight
		}

		set iGapWidth $iInitialGapWidth

		chain
	}

	method fill {width height} {
		set iGapWidth $iInitialGapWidth

		chain $width $height

		updateAttributes [list \$GapWidth $iGapWidth \$InitialGapWidth $iInitialGapWidth]
	}

	protected method doFill {} {
		# Calculate the unreserved width.
		set width [expr $iWidth - ([llength $iLayouts] - 1) * $iGapWidth]
		foreach layout $iLayouts {
			set width [expr $width - [$layout getInitialWidth]]
		}

		# Execute the fill algorithm.
		set width [$iFillAlgorithm $width]

		if {$iFitWidth != 1} {
			# Subtract the remaining width from iWidth.
			set iWidth [expr $iWidth - $width]
		} else {
			# Increase the gap width.
			# Number of elements should be greater than 1 for this to work, otherwise this will be ignored.
			if {[llength $iLayouts] > 1} {
				set iGapWidth [expr $iGapWidth + $width / double([llength $iLayouts] - 1)]
			}
		}
	}
	
	method position {left bottom width height} {
		if {($iFitWidth == 2) && ![Story::isEqual $width $iWidth]} {
			assert {[Story::isGreater $width $iWidth]} {
				Positioning inside too small a space. Was promised a width of $iWidth, got $width.
			}
			# Number of elements should be greater than 1 for this to work, otherwise this will be ignored.
			if {[llength $iLayouts] > 1} {
				set iGapWidth [expr $iGapWidth + ($width - $iWidth) / double([llength $iLayouts] - 1)]
			}

			set iWidth $width

			updateAttributes [list \$Width $iWidth \$GapWidth $iGapWidth]
		}
		if {($iFitHeight == 2) && ![Story::isEqual $height $iHeight]} {
			assert {[Story::isGreater $height $iHeight]} {
				Positioning inside too small a space. Was promised a height of $iHeight, got $height.
			}

			set iHeight $height

			updateAttributes [list \$Height $iHeight]
		}

		chain $left $bottom $width $height	

		# Position the elements.
		if $iLeftToRight {
			set left $iLeft
			foreach layout $iLayouts {
				$layout position $left $iBottom [$layout getWidth] $iHeight
				set left [expr $left + [$layout getWidth] + $iGapWidth]
			}
		} else {
			set left [expr $iLeft + $iWidth + $iGapWidth]
			foreach layout $iLayouts {
				set left [expr $left - [$layout getWidth] - $iGapWidth]
				$layout position $left $iBottom [$layout getWidth] $iHeight
			}
		}
	}

	# Sort the elements according to priority, high to low or reverse. In case of equal priority
	# the order is maintained if high to low, reversed otherwise.
	protected method sortWidthPriority {highToLow} {
		set priorityList {}
		foreach layout $iLayouts {
			set idx 0
			foreach priorLayout $priorityList {
				if {[expr [$priorLayout getWidthPriority] < [$layout getWidthPriority]] == $highToLow} break
				incr idx
			}
			set priorityList [linsert $priorityList $idx $layout] 
		}
		return $priorityList
	}

	# Distribute the non-reserved width according to the priority list and the distribute method.
	# Any extra arguments will be passed as such to the distribute method.
	# In case the height can be adapted, it will be adapted to the element with the highest height
	# priority.  
	protected method distributeWidth {width priorityList distributeMethod undoMethod args} {
		if {$iFitHeight == 1} {
			# No height reduction
			set doIdx 0
		} else {
			# Determine the index of the element with the highest height priority.
			set layout [lindex $priorityList 0]
			set maxHeightPriority [$layout getHeightPriority]
			set minimumHeight [$layout getInitialHeight]
			set heightProdigyIdx 0; set idx 1
			foreach layout [lrange $priorityList 1 end] {
				if {[$layout getHeightPriority] > $maxHeightPriority} {
					set heightProdigyIdx $idx
					set maxHeightPriority [$layout getHeightPriority]
				}
				if {[$layout getInitialHeight] > $minimumHeight} {set minimumHeight [$layout getInitialHeight]}
				incr idx
			}

			# Tentatively fill all layouts up to the height prodigy, giving each the full available height.
			set widths {}
			for {set doIdx 0} {$doIdx <= $heightProdigyIdx} {incr doIdx} {
				lappend widths $width
				set layout [lindex $priorityList $doIdx]
				$layout fill [expr [uplevel [list $distributeMethod $width $layout] $args] + [$layout getInitialWidth]] $iHeight
				set width [expr $width + [$layout getInitialWidth] - [$layout getWidth]]
			}

			# Reduce the height to that of the height prodigy, taking into consideration the minimum 
			# required height for all elements. 
			set iHeight [$layout getHeight]
			if {$iHeight < $minimumHeight} {set iHeight $minimumHeight}

			# Determine the first element which trespassed that height and redo from there.
			for {set redoIdx 0} {$redoIdx < $heightProdigyIdx} {incr redoIdx} {
				set layout [lindex $priorityList $redoIdx]
				if {[$layout getHeight] > $iHeight} break
			}
			if {$redoIdx < $heightProdigyIdx} {
				if [string length $undoMethod] {
					for {set doIdx $heightProdigyIdx} {$doIdx >= $redoIdx} {incr doIdx -1} {
						set width [lindex $widths $doIdx]
						uplevel [list $undoMethod $width [lindex $priorityList $redoIdx]] $args
					}
				} else {
					set width [lindex $widths $redoIdx]
				}
				set doIdx $redoIdx
			}
		}

		for {} {$doIdx < [llength $priorityList]} {incr doIdx} {
			set layout [lindex $priorityList $doIdx]
			$layout fill [expr [uplevel [list $distributeMethod $width $layout] $args] + [$layout getInitialWidth]] $iHeight
			set width [expr $width + [$layout getInitialWidth] - [$layout getWidth]]
		}

		return $width
	}

	# The eager fill algorithm presents each element in turn its initial width plus all of the remaining width.
	# Preferred algorithm for layouts containing images.
	protected method eagerFill {width} {
		distributeWidth $width [sortWidthPriority 1] eagerDistribute {}
	}
	protected method eagerDistribute {width layout} {
		return $width
	}

	# The equal fill algorithm splits its remaining width equally (depending on each element's weight) over
	# all elements, starting with the one with the lowest priority. As such, higher priority elements may
	# potentially receive larger portions.
	# Adequate algorithm for layouts containing no images.
	protected method equalFill {width} {
		set totalWeight 0
		foreach layout $iLayouts {
			set totalWeight [expr $totalWeight + [$layout getHorizontalWeight]]
		}
		distributeWidth $width [sortWidthPriority 0] equalDistribute equalUndo totalWeight
	}
	protected method equalDistribute {width layout totalWeightPtr} {
		upvar $totalWeightPtr totalWeight
		set weight [$layout getHorizontalWeight]
		if {$weight == 0} {return 0}
		set part [expr double($width * $weight / $totalWeight)]
		set totalWeight [expr $totalWeight - $weight]
		return $part
	}
	protected method equalUndo {width layout totalWeightPtr} {
		upvar $totalWeightPtr totalWeight
		set totalWeight [expr $totalWeight + [$layout getHorizontalWeight]]
	}

	variable iFillAlgorithm
	variable iLeftToRight
	variable iGapWidth
	variable iInitialGapWidth
	variable iFitWidth
	variable iFitHeight
}

# VerticalStackLayout.
itcl::class VerticalStackLayout {
	inherit SequenceLayout

	constructor {attributes} {SequenceLayout::constructor $attributes} {}

	protected method doPrune {variablesPtr} {
		upvar $variablesPtr variables

		if [$iAttributes has fillAlgorithm] {
			detect [$iAttributes of fillAlgorithm] variables
		}
		if [$iAttributes has bottomToTop] {
			detect [$iAttributes of bottomToTop] variables
		}
		if [$iAttributes has gapHeight] {
			detect [$iAttributes of gapHeight] variables
		}
		if [$iAttributes has fitWidth] {
			detect [$iAttributes of fitWidth] variables
		}
		if [$iAttributes has fitHeight] {
			detect [$iAttributes of fitHeight] variables
		}

		chain variables
	}

	method prepare {tributes} {
		removeAttributes {$GapHeight $InitialGapHeight}

		chain $tributes
	}

	protected method reset {} {
		chain

		set iFillAlgorithm eagerFill
		set iBottomToTop 0
		set iGapHeight 0
		set iInitialGapHeight 0
		set iFitWidth 0
		set iFitHeight 0
		
		# Set the default vertical weight equal to the number of layouts.
		set iVerticalWeight [llength $iLayouts]
	}

	protected method gather {} {
		chain

		if [$iAttributes has fillAlgorithm] {
			set iFillAlgorithm [evaluate [$iAttributes of fillAlgorithm]]
			assert {[lsearch {eagerFill equalFill} $iFillAlgorithm] != -1} {
				<$iFillAlgorithm> is not a recognized fill algorithm.
			}
		}
		if [$iAttributes has bottomToTop] {
			set iBottomToTop [Story::ofBoolean [evaluate [$iAttributes of bottomToTop]]]
		}
		if [$iAttributes has gapHeight] {
			set iInitialGapHeight [evaluate [$iAttributes of gapHeight]]
			assert {[Story::isGreaterOrEqual $iInitialGapHeight 0]} {
				Illegal gapHeight: [Story::dedouble $iInitialGapHeight] is not a positive number.
			}
		}
		if [$iAttributes has fitWidth] {
			set iFitWidth [evaluate [$iAttributes of fitWidth]]
		}
		if [$iAttributes has fitHeight] {
			set iFitHeight [evaluate [$iAttributes of fitHeight]]
		}
	}

	protected method examine {} {
		assert {[llength $iLayouts] >= 1} {
			A [info class] element requires at least one child element.
		}

		set minimumHeight [expr ([llength $iLayouts] - 1) * $iInitialGapHeight]
		set minimumWidth $iInitialWidth
		foreach layout $iLayouts {
			set minimumHeight [expr $minimumHeight + [$layout getInitialHeight]]
			if {[$layout getInitialWidth] > $minimumWidth} {
				set minimumWidth [$layout getInitialWidth]
			}
		}
		if {[Story::isGreater $minimumHeight $iInitialHeight]} {
			assert {$iLimitHeight != 1} {
				Allowed height too small, need at least another [Story::dedouble [expr $minimumHeight - $iInitialHeight]] points.
			}
			set iInitialHeight $minimumHeight
		}
		if {[Story::isGreater $minimumWidth $iInitialWidth]} {
			assert {$iLimitWidth != 1} {
				Allowed width too small, need at least another [Story::dedouble [expr $minimumWidth - $iInitialWidth]] points.
			}
			set iInitialWidth $minimumWidth
		}

		set iGapHeight $iInitialGapHeight

		chain
	}

	method fill {width height} {
		set iGapHeight $iInitialGapHeight

		chain $width $height

		updateAttributes [list \$GapHeight $iGapHeight \$InitialGapHeight $iInitialGapHeight]
	}

	protected method doFill {} {
		# Calculate the unreserved height.
		set height [expr $iHeight - ([llength $iLayouts] - 1) * $iGapHeight]
		foreach layout $iLayouts {
			set height [expr $height - [$layout getInitialHeight]]
		}

		# Execute the fill algorithm.
		set height [$iFillAlgorithm $height]

		if {$iFitHeight != 1} {
			# Subtract the remaining height from iHeight.
			set iHeight [expr $iHeight - $height]
		} else {
			# Increase the gap height.
			# Number of elements should be greater than 1 for this to work, otherwise this will be ignored.
			if {[llength $iLayouts] > 1} {
				set iGapHeight [expr $iGapHeight + $height / double([llength $iLayouts] - 1)]
			}
		}
	}

	method position {left bottom width height} {
		if {($iFitWidth == 2) && ![Story::isEqual $width $iWidth]} {
			assert {[Story::isGreater $width $iWidth]} {
				Positioning inside too small a space. Was promised a width of $iWidth, got $width.
			}
			set iWidth $width
			updateAttributes [list \$Width $iWidth]
		}
		if {($iFitHeight == 2) && ![Story::isEqual $height $iHeight]} {
			assert {[Story::isGreater $height $iHeight]} {
				Positioning inside too small a space. Was promised a height of $iHeight, got $height.
			}
			# Number of elements should be greater than 1 for this to work, otherwise this will be ignored.
			if {[llength $iLayouts] > 1} {
				set iGapHeight [expr $iGapHeight + ($height - $iHeight) / double([llength $iLayouts] - 1)]
			}

			set iHeight $height

			updateAttributes [list \$Height $iHeight \$GapHeight $iGapHeight]
		}

		chain $left $bottom $width $height
		
		# Position the elements.
		if $iBottomToTop {
			set bottom $iBottom
			foreach layout $iLayouts {
				$layout position $iLeft $bottom $iWidth [$layout getHeight]
				set bottom [expr $bottom + [$layout getHeight] + $iGapHeight]
			}
		} else {
			set bottom [expr $iBottom + $iHeight + $iGapHeight]
			foreach layout $iLayouts {
				set bottom [expr $bottom - [$layout getHeight] - $iGapHeight]
				$layout position $iLeft $bottom $iWidth [$layout getHeight]
			}
		}
	}

	# Sort the elements according to priority, high to low or reverse. In case of equal priority
	# the order is maintained if high to low, reversed otherwise.
	protected method sortHeightPriority {highToLow} {
		set priorityList {}
		foreach layout $iLayouts {
			set idx 0
			foreach priorLayout $priorityList {
				if {[expr [$priorLayout getHeightPriority] < [$layout getHeightPriority]] == $highToLow} break
				incr idx
			}
			set priorityList [linsert $priorityList $idx $layout] 
		}
		return $priorityList
	}

	# Distribute the non-reserved height according to the priority list and the distribute method.
	# Any extra arguments will be passed as such to the distribute method.
	# In case the width can be adapted, it will be adapted to the element with the highest width
	# priority.  
	protected method distributeHeight {height priorityList distributeMethod undoMethod args} {
		if {$iFitWidth == 1} {
			# No width reduction
			set doIdx 0
		} else {
			# Determine the index of the element with the highest width priority.
			set layout [lindex $priorityList 0]
			set maxWidthPriority [$layout getWidthPriority]
			set minimumWidth [$layout getInitialWidth]
			set widthProdigyIdx 0; set idx 1
			foreach layout [lrange $priorityList 1 end] {
				if {[$layout getWidthPriority] > $maxWidthPriority} {
					set widthProdigyIdx $idx
					set maxWidthPriority [$layout getWidthPriority]
				}
				if {[$layout getInitialWidth] > $minimumWidth} {set minimumWidth [$layout getInitialWidth]}
				incr idx
			}

			# Tentatively fill all layouts up to the width prodigy, giving each the full available width.
			set heights {}
			for {set doIdx 0} {$doIdx <= $widthProdigyIdx} {incr doIdx} {
				lappend heights $height
				set layout [lindex $priorityList $doIdx]
				$layout fill $iWidth [expr [uplevel [list $distributeMethod $height $layout] $args] + [$layout getInitialHeight]]
				set height [expr $height + [$layout getInitialHeight] - [$layout getHeight]]
			}

			# Reduce the width to that of the width prodigy, taking into consideration the minimum 
			# required width for all elements. 
			set iWidth [$layout getWidth]
			if {$iWidth < $minimumWidth} {set iWidth $minimumWidth}

			# Determine the first element which trespassed that width and redo from there.
			for {set redoIdx 0} {$redoIdx < $widthProdigyIdx} {incr redoIdx} {
				set layout [lindex $priorityList $redoIdx]
				if {[$layout getWidth] > $iWidth} break
			}
			if {$redoIdx < $widthProdigyIdx} {
				if [string length $undoMethod] {
					for {set doIdx $widthProdigyIdx} {$doIdx >= $redoIdx} {incr doIdx -1} {
						set height [lindex $heights $doIdx]
						uplevel [list $undoMethod $height [lindex $priorityList $doIdx]] $args
					}
				} else {
					set height [lindex $heights $redoIdx]
				}
				set doIdx $redoIdx
			}
		}

		for {} {$doIdx < [llength $priorityList]} {incr doIdx} {
			set layout [lindex $priorityList $doIdx]
			$layout fill $iWidth [expr [uplevel [list $distributeMethod $height $layout] $args] + [$layout getInitialHeight]]
			set height [expr $height + [$layout getInitialHeight] - [$layout getHeight]]
		}

		return $height
	}

	# The eager fill algorithm presents each element in turn its initial height plus all of the remaining height.
	# Preferred algorithm for layouts containing images.
	protected method eagerFill {height} {
		distributeHeight $height [sortHeightPriority 1] eagerDistribute {}
	}
	protected method eagerDistribute {height layout} {
		return $height
	}


	# The equal fill algorithm splits its remaining height equally (depending on each element's weight) over
	# all elements, starting with the one with the lowest priority. As such, higher priority elements may
	# potentially receive larger portions.
	# Adequate algorithm for layouts containing no images.
	protected method equalFill {height} {
		set totalWeight 0
		foreach layout $iLayouts {
			set totalWeight [expr $totalWeight + [$layout getVerticalWeight]]
		}
		distributeHeight $height [sortHeightPriority 0] equalDistribute equalUndo totalWeight
	}
	protected method equalDistribute {height layout totalWeightPtr} {
		upvar $totalWeightPtr totalWeight
		set weight [$layout getVerticalWeight]
		if {$weight == 0} {return 0}
		set part [expr double($height * $weight / $totalWeight)]
		set totalWeight [expr $totalWeight - $weight]
		return $part
	}
	protected method equalUndo {height layout totalWeightPtr} {
		upvar $totalWeightPtr totalWeight
		set totalWeight [expr $totalWeight + [$layout getVerticalWeight]]
	}

	variable iFillAlgorithm
	variable iBottomToTop
	variable iGapHeight
	variable iInitialGapHeight
	variable iFitWidth
	variable iFitHeight
}

# Decorators. Only one child which is subtly adapted.

# DecoratorLayout. Base class. Will provide a dummy layout if no child is present. Therefore 
# Decorators can have zero or one child layout, while its subclasses can pretend to have one.
itcl::class DecoratorLayout {
	inherit Layout

	constructor {attributes} {Layout::constructor $attributes} {}
	destructor {
		if $iPossessed {itcl::delete object $iLayout}
	}

	method setLayoutPath {layoutPath} {
		$iAttributes set layout [Story::toTextList $layoutPath]
	}

	method unsetLayoutPath {} {
		$iAttributes unset layout
	}

	method getLayoutPath {} {
		if {![$iAttributes has layout]} {return ""}
		Story::fromTextList [$iAttributes of layout] remain
	}

	protected method gather {} {
		chain

		set layoutPath [getLayoutPath]
		if {![string length $layoutPath]} {
			if {!$iPossessed} {
				set iLayout [iarray::code [Layout #auto {}]]
				set iPossessed 1
			}
		} else {
			if $iPossessed {
				itcl::delete object $iLayout
				set iPossessed 0
			}
			set iLayout [findLayout $layoutPath]
		}
		$iLayout prepare [$iTributes get]
	}

	protected method examine {} {
		if {[$iLayout getWidthPriority] > $iWidthPriority} {set iWidthPriority [$iLayout getWidthPriority]}
		if {[$iLayout getHeightPriority] > $iHeightPriority} {set iHeightPriority [$iLayout getHeightPriority]}

		chain
	}

	method translate {hor vert} {
		chain $hor $vert

		$iLayout translate $hor $vert
	}

	variable iLayout
	variable iPossessed 0
}

# FrameLayout. Adds a margin around its child layout. In case its width or height are fixed, this margin
# expands to accommadate the child layout, otherwise the margin remains constant and the dimension shrinks
# to fit the child layout.
itcl::class FrameLayout {
	inherit DecoratorLayout

	constructor {attributes} {DecoratorLayout::constructor $attributes} {}

	protected method doPrune {variablesPtr} {
		upvar $variablesPtr variables

		if [$iAttributes has marginWidth] {
			detect [$iAttributes of marginWidth] variables
		}
		if [$iAttributes has marginHeight] {
			detect [$iAttributes of marginHeight] variables
		}
		if [$iAttributes has leftMarginWidth] {
			detect [$iAttributes of leftMarginWidth] variables
		}
		if [$iAttributes has bottomMarginHeight] {
			detect [$iAttributes of bottomMarginHeight] variables
		}
		if [$iAttributes has rightMarginWidth] {
			detect [$iAttributes of rightMarginWidth] variables
		}
		if [$iAttributes has topMarginHeight] {
			detect [$iAttributes of topMarginHeight] variables
		}
		if [$iAttributes has fitWidth] {
			detect [$iAttributes of fitWidth] variables
		}
		if [$iAttributes has fitHeight] {
			detect [$iAttributes of fitHeight] variables
		}

		chain variables
	}

	method prepare {tributes} {
		removeAttributes {$MarginWidth $MarginHeight $InitialMarginWidth $InitialMarginHeight \
			$LeftMarginWidth $BottomMarginHeight $InitialLeftMarginWidth $InitialBottomMarginHeight \
			$RightMarginWidth $TopMarginHeight $InitialRightMarginWidth $InitialTopMarginHeight}

		chain $tributes
	}

	protected method reset {} {
		chain

		set iMarginWidth 0
		set iMarginHeight 0
		set iInitialMarginWidth 0
		set iInitialMarginHeight 0
		set iLeftMarginWidth 0
		set iBottomMarginHeight 0
		set iInitialLeftMarginWidth 0
		set iInitialBottomMarginHeight 0
		set iRightMarginWidth 0
		set iTopMarginHeight 0
		set iInitialRightMarginWidth 0
		set iInitialTopMarginHeight 0
		set iFitWidth 0
		set iFitHeight 0
	}

	protected method gather {} {
		chain

		if [$iAttributes has marginWidth] {
			set iInitialMarginWidth [evaluate [$iAttributes of marginWidth]]
			assert {[Story::isGreaterOrEqual $iInitialMarginWidth 0]} {
				Illegal marginWidth: [Story::dedouble $iInitialMarginWidth] is not a positive number.
			}
		}
		if [$iAttributes has marginHeight] {
			set iInitialMarginHeight [evaluate [$iAttributes of marginHeight]]
			assert {[Story::isGreaterOrEqual $iInitialMarginHeight 0]} {
				Illegal marginHeight: [Story::dedouble $iInitialMarginHeight] is not a positive number.
			}
		}
		if [$iAttributes has leftMarginWidth] {
			set iInitialLeftMarginWidth [evaluate [$iAttributes of leftMarginWidth]]
			assert {[Story::isGreaterOrEqual $iInitialLeftMarginWidth 0]} {
				Illegal leftMarginWidth: [Story::dedouble $iInitialLeftMarginWidth] is not a positive number.
			}
		}
		if [$iAttributes has bottomMarginHeight] {
			set iInitialBottomMarginHeight [evaluate [$iAttributes of bottomMarginHeight]]
			assert {[Story::isGreaterOrEqual $iInitialBottomMarginHeight 0]} {
				Illegal bottomMarginHeight: [Story::dedouble $iInitialBottomMarginHeight] is not a positive number.
			}
		}
		if [$iAttributes has rightMarginWidth] {
			set iInitialRightMarginWidth [evaluate [$iAttributes of rightMarginWidth]]
			assert {[Story::isGreaterOrEqual $iInitialRightMarginWidth 0]} {
				Illegal rightMarginWidth: [Story::dedouble $iInitialRightMarginWidth] is not a positive number.
			}
		}
		if [$iAttributes has topMarginHeight] {
			set iInitialTopMarginHeight [evaluate [$iAttributes of topMarginHeight]]
			assert {[Story::isGreaterOrEqual $iInitialTopMarginHeight 0]} {
				Illegal topMarginHeight: [Story::dedouble $iInitialTopMarginHeight] is not a positive number.
			}
		}
		if [$iAttributes has fitWidth] {
			set iFitWidth [evaluate [$iAttributes of fitWidth]]
		}
		if [$iAttributes has fitHeight] {
			set iFitHeight [evaluate [$iAttributes of fitHeight]]
		}
	}

	protected method examine {} {
		if {$iInitialLeftMarginWidth < $iInitialMarginWidth} {set iInitialLeftMarginWidth $iInitialMarginWidth}
		if {$iInitialRightMarginWidth < $iInitialMarginWidth} {set iInitialRightMarginWidth $iInitialMarginWidth}
		if {$iInitialBottomMarginHeight < $iInitialMarginHeight} {set iInitialBottomMarginHeight $iInitialMarginHeight}
		if {$iInitialTopMarginHeight < $iInitialMarginHeight} {set iInitialTopMarginHeight $iInitialMarginHeight}

		if {$iInitialLeftMarginWidth > $iInitialRightMarginWidth} {
			set iInitialMarginWidth $iInitialLeftMarginWidth
		} else {
			set iInitialMarginWidth $iInitialRightMarginWidth
		}
		if {$iInitialBottomMarginHeight > $iInitialTopMarginHeight} {
			set iInitialMarginHeight $iInitialBottomMarginHeight
		} else {
			set iInitialMarginHeight $iInitialTopMarginHeight
		}

		set minimumWidth [expr [$iLayout getInitialWidth] + ($iInitialLeftMarginWidth + $iInitialRightMarginWidth)]
		if {[Story::isGreater $minimumWidth $iInitialWidth]} {
			assert {$iLimitWidth != 1} {
				Allowed width too small, need at least another [Story::dedouble [expr $minimumWidth - $iInitialWidth]] points.
			}
			set iInitialWidth $minimumWidth
		}
		set minimumHeight [expr [$iLayout getInitialHeight] + ($iInitialBottomMarginHeight + $iInitialTopMarginHeight)]
		if {[Story::isGreater $minimumHeight $iInitialHeight]} {
			assert {$iLimitHeight != 1} {
				Allowed height too small, need at least another [Story::dedouble [expr $minimumHeight - $iInitialHeight]] points.
			}
			set iInitialHeight $minimumHeight
		}

		set iMarginWidth $iInitialMarginWidth
		set iMarginHeight $iInitialMarginHeight
		set iLeftMarginWidth $iInitialLeftMarginWidth
		set iBottomMarginHeight $iInitialBottomMarginHeight
		set iRightMarginWidth $iInitialRightMarginWidth
		set iTopMarginHeight $iInitialTopMarginHeight

		chain
	}

	method fill {width height} {
		set iMarginWidth $iInitialMarginWidth
		set iMarginHeight $iInitialMarginHeight
		set iLeftMarginWidth $iInitialLeftMarginWidth
		set iBottomMarginHeight $iInitialBottomMarginHeight
		set iRightMarginWidth $iInitialRightMarginWidth
		set iTopMarginHeight $iInitialTopMarginHeight

		chain $width $height

		updateAttributes [list \$MarginWidth $iMarginWidth \$MarginHeight $iMarginHeight \
			\$InitialMarginWidth $iInitialMarginWidth \$InitialMarginHeight $iInitialMarginHeight \
			\$LeftMarginWidth $iLeftMarginWidth \$BottomMarginHeight $iBottomMarginHeight \
			\$InitialLeftMarginWidth $iInitialLeftMarginWidth \$InitialBottomMarginHeight $iInitialBottomMarginHeight \
			\$RightMarginWidth $iRightMarginWidth \$TopMarginHeight $iTopMarginHeight \
			\$InitialRightMarginWidth $iInitialRightMarginWidth \$InitialTopMarginHeight $iInitialTopMarginHeight]
	}

	protected method doFill {} {
		set totalMarginWidth [expr $iLeftMarginWidth + $iRightMarginWidth]
		set totalMarginHeight [expr $iBottomMarginHeight + $iTopMarginHeight]

		$iLayout fill [expr $iWidth - $totalMarginWidth] [expr $iHeight - $totalMarginHeight]

		if {$iFitWidth != 1} {
			set iWidth [expr [$iLayout getWidth] + $totalMarginWidth]
		}
		if {$iFitHeight != 1} {
			set iHeight [expr [$iLayout getHeight] + $totalMarginHeight]
		}
	}

	method position {left bottom width height} {
		if {($iFitWidth == 2) && ![Story::isEqual $width $iWidth]} {
			assert {[Story::isGreater $width $iWidth]} {
				Positioning inside too small a space. Was promised a width of $iWidth, got $width.
			}

			set iWidth $width

			updateAttributes [list \$Width $iWidth]
		}
		if {($iFitHeight == 2) && ![Story::isEqual $height $iHeight]} {
			assert {[Story::isGreater $height $iHeight]} {
				Positioning inside too small a space. Was promised a height of $iHeight, got $height.
			}

			set iHeight $height

			updateAttributes [list \$Height $iHeight]
		}

		chain $left $bottom $width $height
		
		$iLayout position [expr $iLeft + $iLeftMarginWidth] [expr $iBottom + $iBottomMarginHeight] \
			[expr $iWidth - ($iLeftMarginWidth + $iRightMarginWidth)] [expr $iHeight - ($iBottomMarginHeight + $iTopMarginHeight)]
			
		set iLeftMarginWidth [expr [$iLayout getLeft] - $iLeft]
		set iRightMarginWidth [expr $iLeft + $iWidth - ([$iLayout getLeft] + [$iLayout getWidth])]
		if {$iLeftMarginWidth > $iRightMarginWidth} {
			set iMarginWidth $iLeftMarginWidth
		} else {
			set iMarginWidth $iRightMarginWidth
		}
		set iBottomMarginHeight [expr [$iLayout getBottom] - $iBottom]
		set iTopMarginHeight [expr $iBottom + $iHeight - ([$iLayout getBottom] + [$iLayout getHeight])]
		if {$iBottomMarginHeight > $iTopMarginHeight} {
			set iMarginHeight $iBottomMarginHeight
		} else {
			set iMarginHeight $iTopMarginHeight
		}
		
		updateAttributes [list \$LeftMarginWidth $iLeftMarginWidth \$RightMarginWidth $iRightMarginWidth \
			\$BottomMarginHeight $iBottomMarginHeight \$TopMarginHeight $iTopMarginHeight \
			\$MarginWidth $iMarginWidth \$MarginHeight $iMarginHeight]
	}

	variable iMarginWidth
	variable iMarginHeight
	variable iInitialMarginWidth
	variable iInitialMarginHeight
	variable iLeftMarginWidth
	variable iBottomMarginHeight
	variable iInitialLeftMarginWidth
	variable iInitialBottomMarginHeight
	variable iRightMarginWidth
	variable iTopMarginHeight
	variable iInitialRightMarginWidth
	variable iInitialTopMarginHeight
	variable iFitWidth
	variable iFitHeight
}

# GridLayout. Snaps width and height to an underlying grid. 
itcl::class GridLayout {
	inherit DecoratorLayout

	constructor {attributes} {DecoratorLayout::constructor $attributes} {}

	protected method doPrune {variablesPtr} {
		upvar $variablesPtr variables

		if [$iAttributes has cellWidth] {
			detect [$iAttributes of cellWidth] variables
		}
		if [$iAttributes has cellHeight] {
			detect [$iAttributes of cellHeight] variables
		}
		if [$iAttributes has lineLength] {
			detect [$iAttributes of lineLength] variables
		}
		if [$iAttributes has boxLength] {
			detect [$iAttributes of boxLength] variables
		}
		if [$iAttributes has fitWidth] {
			detect [$iAttributes of fitWidth] variables
		}
		if [$iAttributes has fitHeight] {
			detect [$iAttributes of fitHeight] variables
		}

		chain variables
	}

	method prepare {tributes} {
		removeAttributes {$CellWidth $CellHeight $LineLength $BoxLength $InitialLineLength $InitialBoxLength}

		chain $tributes
	}

	protected method reset {} {
		chain

		set iCellWidth 0
		set iCellHeight 0
		set iLineLength 0
		set iBoxLength 0
		set iInitialLineLength 0
		set iInitialBoxLength 0
		set iFitWidth 0
		set iFitHeight 0
	}

	protected method gather {} {
		chain

		if [$iAttributes has cellWidth] {
			set iCellWidth [evaluate [$iAttributes of cellWidth]]
			assert {[Story::isGreaterOrEqual $iCellWidth 0]} {
				Illegal cellWidth: [Story::dedouble $iCellWidth] is not a positive number.
			}
		}
		if [$iAttributes has cellHeight] {
			set iCellHeight [evaluate [$iAttributes of cellHeight]]
			assert {[Story::isGreaterOrEqual $iCellHeight 0]} {
				Illegal cellHeight: [Story::dedouble $iCellHeight] is not a positive number.
			}
		}
		if [$iAttributes has lineLength] {
			set iInitialLineLength [evaluate [$iAttributes of lineLength]]
			assert {[string is integer $iInitialLineLength] && ($iInitialLineLength >= 0)} {
				Illegal lineLength: <$iInitialLineLength> is not a positive integer.
			}
		}
		if [$iAttributes has boxLength] {
			set iInitialBoxLength [evaluate [$iAttributes of boxLength]]
			assert {[string is integer $iInitialBoxLength] && ($iInitialBoxLength >= 0)} {
				Illegal boxLength: <$iInitialBoxLength> is not a positive integer.
			}
		}
		if [$iAttributes has fitWidth] {
			set iFitWidth [evaluate [$iAttributes of fitWidth]]
		}
		if [$iAttributes has fitHeight] {
			set iFitHeight [evaluate [$iAttributes of fitHeight]]
		}
	}

	protected method examine {} {
		assert {[Story::isGreater $iCellWidth 0] && [Story::isGreater $iCellHeight 0]} {
			[info class] requires cellWidth [Story::dedouble $iCellWidth] and cellHeight [[Story::dedouble $iCellHeight] to be greater than 0.
		}

		set minimumWidth [$iLayout getInitialWidth]
		if {[Story::isGreater $minimumWidth $iInitialWidth]} {
			assert {$iLimitWidth != 1} {
				Allowed width too small, need at least another [Story::dedouble [expr $minimumWidth - $iInitialWidth]] points.
			}
			set iInitialWidth $minimumWidth
		}
		set minimumHeight [$iLayout getInitialHeight]
		if {[Story::isGreater $minimumHeight $iInitialHeight]} {
			assert {$iLimitHeight != 1} {
				Allowed height too small, need at least another [Story::dedouble [expr $minimumHeight - $iInitialHeight]] points.
			}
			set iInitialHeight $minimumHeight
		}

		if {$iInitialLineLength * $iCellWidth < $iInitialWidth} {
			if {$iLimitWidth != 1} {
				set iInitialLineLength [Story::roundUp $iInitialWidth $iCellWidth]
			} else {
				set iInitialLineLength [Story::roundDown $iInitialWidth $iCellWidth]
			}
		}
		if {$iInitialBoxLength * $iCellHeight < $iInitialHeight} {
			if {$iLimitHeight != 1} {
				set iInitialBoxLength [Story::roundUp $iInitialHeight $iCellHeight]
			} else {
				set iInitialBoxLength [Story::roundDown $iInitialHeight $iCellHeight]
			}
		}

		set iInitialWidth [expr $iInitialLineLength * $iCellWidth]
		set iInitialHeight [expr $iInitialBoxLength * $iCellHeight]

		set iLineLength $iInitialLineLength
		set iBoxLength $iInitialBoxLength

		chain
	}

	method fill {width height} {
		set iLineLength $iInitialLineLength
		set iBoxLength $iInitialBoxLength

		chain $width $height

		updateAttributes [list \$CellWidth $iCellWidth \$CellHeight $iCellHeight \
			\$LineLength $iLineLength \$BoxLength $iBoxLength \
			\$InitialLineLength $iInitialLineLength \$InitialBoxLength $iInitialBoxLength]
	}

	protected method doFill {} {
		if {$iLimitWidth == 0} {
			set iLineLength [Story::roundDown $iWidth $iCellWidth]
			set iWidth [expr $iLineLength * $iCellWidth]
		}
		if {$iLimitHeight == 0} {
			set iBoxLength [Story::roundDown $iHeight $iCellHeight]
			set iHeight [expr $iBoxLength * $iCellHeight]
		}

		$iLayout fill $iWidth $iHeight

		if {$iFitWidth != 1} {
			set iLineLength [Story::roundUp [$iLayout getWidth] $iCellWidth]
			set iWidth [expr $iLineLength * $iCellWidth]
		}
		if {$iFitHeight != 1} {
			set iBoxLength [Story::roundUp [$iLayout getHeight] $iCellHeight]
			set iHeight [expr $iBoxLength * $iCellHeight]
		}
	}

	method position {left bottom width height} {
		if {($iFitWidth == 2) && ![Story::isEqual $width $iWidth]} {
			assert {[Story::isGreater $width $iWidth]} {
				Positioning inside too small a space. Was promised a width of $iWidth, got $width.
			}

			set iLineLength [Story::roundDown $width $iCellWidth]
			set iWidth [expr $iLineLength * $iCellWidth]

			updateAttributes [list \$Width $iWidth \$LineLength $iLineLength]
		}
		if {($iFitHeight == 2) && ![Story::isEqual $height $iHeight]} {
			assert {[Story::isGreater $height $iHeight]} {
				Positioning inside too small a space. Was promised a height of $iHeight, got $height.
			}

			set iBoxLength [Story::roundDown $height $iCellHeight]
			set iHeight [expr $iBoxLength * $iCellHeight]

			updateAttributes [list \$Height $iHeight \$BoxLength $iBoxLength]
		}

		chain $left $bottom $width $height
		
		$iLayout position $iLeft $iBottom $iWidth $iHeight
	}

	variable iCellWidth
	variable iCellHeight
	variable iLineLength
	variable iBoxLength
	variable iInitialLineLength
	variable iInitialBoxLength
	variable iFitWidth
	variable iFitHeight
}
